﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using Athena;
using Athena.Functions;
using Athena.Core;
using Athena.Plugins;
using WeifenLuo.WinFormsUI.Docking;
using System.Threading;

using Microsoft.Win32;

namespace Rplugin
{
    public partial class ScriptWindow : Athena.Tools.IGUITool
    {

        //static Process _Rslave;

        public override void SetupToolStrip(ToolStrip oStrip)
        {
            ToolStripButton oButton = new ToolStripButton();
            oButton.Text = Text;
            oButton.ToolTipText = "Analyze any module on the screen using R";
            oButton.Click += new EventHandler(ShowHide_Click);
            oStrip.Items.Add(oButton);
        }

        public override void SetupDockPanel(WeifenLuo.WinFormsUI.Docking.DockPanel oPanel)
        {
            this.DockPanel = oPanel;
            Hide();
        }

        public override void ShowHide_Click(object sender, EventArgs e)
        {
            if (IsHidden)
            {
                Show();
            }
            else
            {
                Hide();                
            }
        }

        public override void Show()
        {
            base.Show();
            LoadModules();
            inputTextBox.Text = _Text;
        }

        public ScriptWindow()
        {
            InitializeComponent();

            SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint, true);
            
            Text = "Statistics/Coding";
            _bRequiresNewToolWindow = true;
            ToolManager.Instance.RegisterTool(Text, this);
            outputTextBox.OnCommandExecution += new Athena.Tools.CommandTextBox.OnCommandExecutionDelegate(outputTextBox_CommandInvoked);
        }

        private static ZedGraph.ColorSymbolRotator _Rotator = new ZedGraph.ColorSymbolRotator();
        string[] _ColNames;
        double[,] _Data;
        string _Text = "";

        void ReadTable(string filename)
        {
            StreamReader oStream = new StreamReader(new FileStream(filename, FileMode.Open));
            
            string line = oStream.ReadLine();
            if (!string.IsNullOrEmpty(line))
            {
                _ColNames = line.Split(new char[]{'\t'});
                for (int i = 0; i < _ColNames.Length; ++i)
                {
                    if (string.IsNullOrEmpty(_ColNames[i]))
                        _ColNames[i] = String.Format("col{0}",(i+1));
                }

                List<double[]> data = new List<double[]>();
                line = oStream.ReadLine();

                while (!oStream.EndOfStream)
                {
                    string[] words = line.Split(new char[]{'\t'});
                    double[] values = new double[words.Length];
                    for (int i = 0; i < words.Length; ++i)
                        values[i] = Double.Parse(words[i]);
                    data.Add(values);
                    line = oStream.ReadLine();
                }

                _Data = new double[data.Count, _ColNames.Length];

                for (int i = 0; i < data.Count; ++i)
                    for (int j = 0; j < _ColNames.Length; ++j)
                        _Data[i, j] = data[i][j];
            }
            oStream.Close();
            oStream.Dispose();
        }

        private void PrintResults(ZedGraph.ZedGraphControl zedGraph, string[] colNames, double[,] oData, int xaxis)
        {
            List<System.Drawing.Color> colors = new List<Color>();
            _Rotator = new ZedGraph.ColorSymbolRotator();

            for (int i=0; i < colNames.Length; ++i)
                colors.Add(_Rotator.NextColor);

            zedGraph.GraphPane.CurveList.Clear();
            zedGraph.GraphPane.XAxis.Title.Text = colNames[0];
            if (colNames.Length > 1)
            {
                if (colNames.Length > 2)
                    zedGraph.GraphPane.YAxis.Title.Text = colNames[1] + "..." + colNames[colNames.Length - 1];
                else
                    zedGraph.GraphPane.YAxis.Title.Text = colNames[1];
            }
            zedGraph.GraphPane.Title.Text = "";
            zedGraph.GraphPane.XAxis.Scale.MinGrace = 0.0;
            zedGraph.GraphPane.XAxis.Scale.MaxGrace = 0.0;
            //_oZedGraph.GraphPane.YAxis.Scale.MinGrace = 0.0;
            //_oZedGraph.GraphPane.YAxis.Scale.MaxGrace = 5.0;

            for (int i = 1; i < oData.GetLength(1); i++)
            {
                if (i == xaxis) continue;
                ZedGraph.PointPairList oList = new ZedGraph.PointPairList();
                for (int j = 0; j < oData.GetLength(0); j++)
                {
                    oList.Add(oData[j, xaxis], oData[j, i]);
                }
                ZedGraph.LineItem oItem = zedGraph.GraphPane.AddCurve(colNames[i], oList, colors[i], ZedGraph.SymbolType.None);
                oItem.Line.Width *= 2f;
            }
            zedGraph.AxisChange();
            zedGraph.Refresh();
        }

        string ReadFile(string filename, bool start)
        {
            StreamReader oStream = new StreamReader(new FileStream(filename, FileMode.Open));
            StringBuilder file = new StringBuilder();
            string line = "";
            while (!oStream.EndOfStream)
            {
                line = oStream.ReadLine();
                if (line.Trim() == "user  system elapsed")
                    break;
                if (!string.IsNullOrEmpty(line))
                {
                    if (line[0] == '>' || line[0] == '+')
                    {
                        start = true;
                    }
                    else
                        if (start)
                        {
                            file.AppendLine(line);
                        }
                }
            }
            oStream.Close();
            oStream.Dispose();
            return file.ToString();
        }

        void WriteFile(string filename, string text, FileMode mode)
        {
            StreamWriter oStream = new StreamWriter(new FileStream(filename, mode));
            oStream.Write(text);
            oStream.Close();
            oStream.Dispose();
        }
        
        /// <summary>
        /// Generates the R code for the given module
        /// </summary>
        /// <param name="module">module to generate R code for</param>
        /// <returns>StringBuilder representing the R code</returns>
        StringBuilder GenerateRCode(Module module)
        {
            Matrix N = module.StoichiometryMatrix;
            StringBuilder rcode = new StringBuilder();

            if (N.c == 0 || N.r == 0) return rcode;

            string ID = module.ID;

            rcode.Append(String.Format("{0} = list();",ID));  //M = list();
            rcode.Append(Environment.NewLine);

            List<string> species = new List<string>();
            List<string> reactions = new List<string>();
            List<string> parms = new List<string>();
            List<string> inits = new List<string>();

            Hashtable allItems = module.AllItems;
            Hashtable allParams = module.GlobalParameters;

            foreach (string key in allItems.Keys)
            {
                Species x = allItems[key] as Species;
                if (x is Species)
                {
                    if (x.Boundary)
                    {
                        inits.Add(String.Format("{0}={1}", key, x.Concentration));
                        parms.Add(String.Format("\"{0}\"", key));
                    }
                    else
                    {
                        inits.Add(String.Format("{0}={1}", key, x.Concentration));
                        //species.Add(String.Format("\"{0}\"", key));
                    }
                }
            }

            //make sure that N and species are in same order
            for (int i=0; i < N.r; ++i)
                species.Add(String.Format("\"{0}\"", N.rowNames[i]));

            for (int i = 0; i < N.c; ++i)
                reactions.Add(String.Format("\"{0}\"", N.columnNames[i]));

            foreach (string key in allParams.Keys)
            {
                Parameter x = allParams[key] as Parameter;
                if (x is Parameter)
                {
                    inits.Add(String.Format("{0}={1}", key, x.Value));
                    parms.Add(String.Format("\"{0}\"", key));
                }
            }
            
            rcode.Append(String.Format("temp = c({0});",String.Join(",",inits.ToArray()))); //temp = c(s1=1, s2=0.5, k0 =.1, ....)
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("{0}[1:length(temp)] = temp;", ID)); //M[1:length(temp)] = temp
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("names({0}) = names(temp);", ID)); //names(M) = names(temp)
            rcode.Append(Environment.NewLine);

            rcode.Append("rm(temp);");
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("{0}$species = c({1});", ID, String.Join(",", species.ToArray()))); //M$species = c("x","y",...)
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("{0}$params = c({1});", ID, String.Join(",", parms.ToArray()))); //M$params = c("k0","k1",...)
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("{0}$name = \"{0}\";", ID)); //M$name = "M"
            rcode.Append(Environment.NewLine);

            //M$flux = function { .... }
            rcode.Append(String.Format("{0}$flux = function(y.values)",ID));
            rcode.Append(Environment.NewLine);
            rcode.Append("{");
            rcode.Append(Environment.NewLine);

            rcode.Append(String.Format("   y.params = as.numeric(matrix({0}[{0}$params]));",ID));
            rcode.Append(Environment.NewLine);

            for (int i = 0; i < parms.Count; ++i)
            {
                rcode.Append(String.Format("   {0} = y.params[{1}];", parms[i].Replace("\"", ""), i + 1));
                rcode.Append(Environment.NewLine);
            }

            for (int i = 0; i < species.Count; ++i)
            {
                rcode.Append(String.Format("   {0} = y.values[{1}];", species[i].Replace("\"", ""), i + 1));
                rcode.Append(Environment.NewLine);
            }

            //List<string> reactionNames = new List<string>();

            Hashtable rules = module.GlobalRules;
            foreach (string key in rules.Keys)  //name = Rule ... 
            {
                string rate = rules[key] as string;
                if (rate != null)
                {
                    rcode.Append(String.Format("   {0} = {1};", key, rate));
                    rcode.Append(Environment.NewLine);
                }
            }

            foreach (string key in allItems.Keys)  //name = PoPS ... 
            {
                Part p = allItems[key] as Part;
                if (p != null)
                {
                    string rate = p.PoPS.GetRate(module, allItems);
                    rcode.Append(String.Format("   {0} = {1};", key, rate));
                    rcode.Append(Environment.NewLine);
                }
            }

            foreach (string key in allItems.Keys)  //name = rate ... 
            {
                Edge e = allItems[key] as Edge;
                if (e != null && e.IsReaction)
                {
                    string rate = e.GetRate(module,allItems);
                    rcode.Append(String.Format("   {0} = {1};",key,rate));
                    rcode.Append(Environment.NewLine);
                    //reactionNames.Add(key);
                }
            }

            rcode.Append(String.Format("   c({0});",String.Join(",",N.columnNames)));
            rcode.Append(Environment.NewLine);
            rcode.Append("}");
            rcode.Append(Environment.NewLine);

            //M$f = function { .... }
            rcode.Append(String.Format("{0}$f = function(Time,y.values,y.params)", ID));
            rcode.Append(Environment.NewLine);
            rcode.Append("{");
            rcode.Append(Environment.NewLine);

            for (int i = 0; i < parms.Count; ++i)
            {
                rcode.Append(String.Format("   {0} = y.params[{1}];", parms[i].Replace("\"", ""), i + 1));
                rcode.Append(Environment.NewLine);
            }

            for (int i = 0; i < species.Count; ++i)
            {
                rcode.Append(String.Format("   {0} = y.values[{1}];", species[i].Replace("\"", ""), i + 1));
                rcode.Append(Environment.NewLine);
            }

            //List<string> reactionNames = new List<string>();

            foreach (string key in rules.Keys)  //name = Rule ... 
            {
                string rate = rules[key] as string;
                if (rate != null)
                {
                    rcode.Append(String.Format("   {0} = {1};", key, rate));
                    rcode.Append(Environment.NewLine);
                }
            }

            foreach (string key in allItems.Keys)  //name = PoPS ... 
            {
                Part p = allItems[key] as Part;
                if (p != null)
                {
                    string rate = p.PoPS.GetRate(module, allItems);
                    rcode.Append(String.Format("   {0} = {1};", key, rate));
                    rcode.Append(Environment.NewLine);
                }
            }

            foreach (string key in allItems.Keys)  //name = rate ... 
            {
                Edge e = allItems[key] as Edge;
                if (e != null && e.IsReaction)
                {
                    string rate = e.GetRate(module,allItems);
                    rcode.Append(String.Format("   {0} = {1};",key,rate));
                    rcode.Append(Environment.NewLine);
                    //reactionNames.Add(key);
                }
            }

            List<string> diffNames = new List<string>();
            for (int i = 0; i < species.Count; ++i)
            {
                string s = species[i].Replace("\"", "");
                rcode.Append(String.Format("   diff_{0} = 0", s));
                diffNames.Add(String.Format("diff_{0}", s));
                for (int j = 0; j < N.c; ++j) //get rates of change for species
                {
                    if (N[i, j] != 0)
                    {
                        if (N[i, j] < 0)
                            rcode.Append(String.Format("{0}*{1}", N[i, j], N.columnNames[j]));
                        else
                            rcode.Append(String.Format("+{0}*{1}", N[i, j], N.columnNames[j]));
                    }
                }
                rcode.Append(";");
                rcode.Append(Environment.NewLine);
            }
            rcode.Append(String.Format("   list(c({0}));", String.Join(",", diffNames.ToArray())));
            rcode.Append(Environment.NewLine);
            rcode.Append("}");
            rcode.Append(Environment.NewLine);

            //stoic matrix
            if (N.r > 0 && N.c > 0)
            {
                string[] sN = new string[N.r * N.c];
                int k = 0;
                for (int j = 0; j < N.c; ++j)
                    for (int i = 0; i < N.r; ++i)
                    {
                        sN[k] = N[i, j].ToString();
                        ++k;
                    }

                rcode.Append(String.Format("{0}$N = matrix(c({1}),{2},{3})", ID, String.Join(",", sN), N.r, N.c));
                rcode.Append(Environment.NewLine);
                rcode.Append(String.Format("rownames({0}$N) = c({1});", ID, String.Join(",", species.ToArray())));
                rcode.Append(Environment.NewLine);
                rcode.Append(String.Format("colnames({0}$N) = c({1});", ID, String.Join(",", reactions.ToArray())));
                rcode.Append(Environment.NewLine);
            }

            //phew!
            return rcode;
        }

        StringBuilder _Rcode = null;
        void LoadModules()
        {
            try
            {
                Utility.SetMessage("Loading all modules into R...");

                List<Item> modules = CurrentModel.BackEndModel.AllItemsOfType(typeof(Module));
                modules.Add(CurrentModel.BackEndModel);

                _Rcode = new StringBuilder();
                //_Rcode.Append("source(\"Rplugin\\\\Dscript_sim.R\")");
                //_Rcode.Append(Environment.NewLine);

                List<string> names = new List<string>();

                foreach (Item i in modules)
                {
                    Module m = i as Module;
                    if (m != null && !names.Contains(m.ID))
                    {
                        names.Add(m.ID); //avoid double declaration using same name
                        _Rcode.Append(Environment.NewLine);
                        _Rcode.Append(String.Format("#module {0}", m.ID)); //comments in R code
                        _Rcode.Append(Environment.NewLine);
                        _Rcode.Append(GenerateRCode(m));
                        _Rcode.Append(Environment.NewLine);
                    }
                }
                
                _oZedGraph.GraphPane.CurveList.Clear();
                _oZedGraph.Refresh();

                File.Delete(".Rdata");

                Utility.SetMessage("Modules loaded into R");
            }
            catch (Exception)
            {
                outputTextBox.Text = "R model did not load correctly";
                Utility.SetMessage("R model did not load correctly");
            }

            //try
            //{
                runR(_Rcode.ToString());

                /*
                if (!string.IsNullOrEmpty(sPath))
                {
                    _Rslave = new Process();
                    _Rslave.StartInfo = new ProcessStartInfo("R", "--slave");
                    _Rslave.StartInfo.CreateNoWindow = true;
                    _Rslave.StartInfo.UseShellExecute = false;
                    _Rslave.StartInfo.WorkingDirectory = Utility.CurrentDirectory;
                    _Rslave.StartInfo.RedirectStandardInput = true;
                    _Rslave.StartInfo.RedirectStandardOutput = true;
                    _Rslave.Start();          
                }
                else
                {
                    _Rslave = new Process();
                    _Rslave.StartInfo = new ProcessStartInfo("R", "--slave");
                    _Rslave.StartInfo.CreateNoWindow = true;
                    _Rslave.StartInfo.UseShellExecute = false;
                    _Rslave.StartInfo.WorkingDirectory = Utility.CurrentDirectory;
                    _Rslave.StartInfo.RedirectStandardInput = true;
                    _Rslave.StartInfo.RedirectStandardOutput = true;
                    _Rslave.Start();
                }
            }
            catch (Exception)
            {
                outputTextBox.Text = "R program was not able to start";
            }*/
        }

        private void reloadButton_Click(object sender, EventArgs e)
        {
            LoadModules();            
        }

        static string rout = "";
        /*
        static void RunRCommand()
        {
            rout = "";
            while (_Rslave.StandardOutput.Peek() != -1)
                rout += _Rslave.StandardOutput.ReadLine();
        }
        */

        private string runR(string command)
        {
            rout = "";
            try
            {

                Utility.SetMessage("starting R...");

                string sPath = (string)Registry.LocalMachine.OpenSubKey("SOFTWARE\\R-core\\R").GetValue("InstallPath");

                string dir = Utility.CurrentDirectory.Replace("\\", "\\\\");

                WriteFile("model.R",
                    "source(\"" + dir + "\\\\Rplugin\\\\Dscript_sim.R\")" + Environment.NewLine + command,
                    FileMode.Create);

                if (!string.IsNullOrEmpty(sPath))
                {
                    ProcessStartInfo info = new ProcessStartInfo("\"" + sPath + "\\bin\\R" + "\"", "CMD BATCH model.R");
                    info.CreateNoWindow = true;
                    info.UseShellExecute = false;
                    //info.WorkingDirectory = Utility.CurrentDirectory;
                    Process proc = Process.Start(info);

                    proc.WaitForExit();
                }
                else
                {
                    ProcessStartInfo info = new ProcessStartInfo("R", "CMD BATCH model.R");
                    info.CreateNoWindow = true;
                    info.UseShellExecute = false;
                    //info.WorkingDirectory = Utility.CurrentDirectory;
                    Process proc = Process.Start(info);
                    proc.WaitForExit();
                }

                rout = ReadFile("model.Rout", false);
                
                /*
                command += Environment.NewLine + "print(\"\")";
                _Rslave.StandardInput.WriteLine(command);

                Thread t = new Thread(new ThreadStart(RunRCommand));
                t.Start();
                */
                if (command.Contains("graph("))
                {
                    //t.Join(10000);
                    ReadTable("Rplot.out");
                    PrintResults(_oZedGraph, _ColNames, _Data, 0);
                    File.Delete("Rplot.out");
                }
                else
                {
                    //t.Join(1000);
                }
                
                Utility.SetMessage("Finished running R command(s)");
            }
            catch (Exception)
            {
                Utility.SetMessage("Error Running R code");
            }
            return rout + Environment.NewLine;
        }

        private void runButton_Click(object sender, EventArgs e)
        {
            _Text = inputTextBox.Text;

            string selected = inputTextBox.SelectedText;
            if (selected.Length > 0)
                outputTextBox.Text = runR(selected);
            else
                outputTextBox.Text = runR(_Text) + ">>";
        }

        private void consoleButton_Click(object sender, EventArgs e)
        {
            try
            {
                string sPath = (string)Registry.LocalMachine.OpenSubKey("SOFTWARE\\R-core\\R").GetValue("InstallPath");

                if (!string.IsNullOrEmpty(sPath))
                {
                    /*if (sPath.Contains(" "))
                    {
                        sPath.Replace("\\\\", "\\");
                        sPath = "\"" + sPath + "\"";
                    }*/

                    ProcessStartInfo info = new ProcessStartInfo("\"" + sPath + "\\bin\\R" + "\"", ".Rdata");
                    //info.CreateNoWindow = true;
                    //info.UseShellExecute = false;
                    info.WorkingDirectory = Utility.CurrentDirectory;
                    Process proc = Process.Start(info);

                    //proc.WaitForExit();
                }
                else
                {
                    ProcessStartInfo info = new ProcessStartInfo("R", ".Rdata");
                    //info.CreateNoWindow = true;
                    //info.UseShellExecute = false;
                    info.WorkingDirectory = Utility.CurrentDirectory;
                    Process proc = Process.Start(info);
                    //proc.WaitForExit();
                }
            }
            catch (Exception)
            {
                Utility.SetMessage("Error Running R code");
            }
        }

        /// <summary>
        /// Prevent some nasty keys ...
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (!Focused) return false;

            switch (keyData)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Back:
                case Keys.Tab:
                case Keys.Enter:
                case Keys.Home:
                case Keys.Home & Keys.Shift:
                    return true;

                default:
                    return base.ProcessCmdKey(ref msg, keyData);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            helpscreen H = new helpscreen();
            H.Show();
        }

        void outputTextBox_CommandInvoked(string text, EventArgs e)
        {
            string rout = runR(text);
            outputTextBox.Text += rout;
        }

    }
}
